#region Header
/**
 * JsonData.cs
 *   Generic type to hold JSON data (objects, arrays, and so on). This is
 *   the default type returned by JsonMapper.ToObject().
 *
 * The authors disclaim copyright to this source code. For more details, see
 * the COPYING file included with this distribution.
 **/
#endregion


using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;

#if UNITY3D
using UnityEngine;
#endif


namespace LitJson
{
	public class JsonData : IJsonWrapper, IEquatable<JsonData>
	{
		#region Fields

		private IList<JsonData> inst_array;
		private bool inst_boolean;
		private double inst_double;
		private int inst_int;
		private long inst_long;
		private IDictionary<string, JsonData> inst_object;
		private string inst_string;
		private string json;
		private JsonType type;

		// Used to implement the IOrderedDictionary interface
		private IList<KeyValuePair<string, JsonData>> object_list;

		#endregion


		#region Properties

		public int Count {
			get { return EnsureCollection ().Count; }
		}

		public bool IsArray {
			get { return type == JsonType.Array; }
		}

		public bool IsBoolean {
			get { return type == JsonType.Boolean; }
		}

		public bool IsDouble {
			get { return type == JsonType.Double; }
		}

		public bool IsInt {
			get { return type == JsonType.Int; }
		}

		public bool IsLong {
			get { return type == JsonType.Long; }
		}

		public bool IsObject {
			get { return type == JsonType.Object; }
		}

		public bool IsString {
			get { return type == JsonType.String; }
		}

		public ICollection<string> Keys {
			get {
				EnsureDictionary ();
				return inst_object.Keys;
			}
		}

		#endregion


		#region ICollection Properties

		int ICollection.Count {
			get {
				return Count;
			}
		}

		bool ICollection.IsSynchronized {
			get {
				return EnsureCollection ().IsSynchronized;
			}
		}

		object ICollection.SyncRoot {
			get {
				return EnsureCollection ().SyncRoot;
			}
		}

		#endregion


		#region IDictionary Properties

		bool IDictionary.IsFixedSize {
			get {
				return EnsureDictionary ().IsFixedSize;
			}
		}

		bool IDictionary.IsReadOnly {
			get {
				return EnsureDictionary ().IsReadOnly;
			}
		}

		ICollection IDictionary.Keys {
			get {
				EnsureDictionary ();
				IList<string> keys = new List<string> ();

				foreach (KeyValuePair<string, JsonData> entry in
                         object_list) {
					keys.Add (entry.Key);
				}

				return (ICollection)keys;
			}
		}

		ICollection IDictionary.Values {
			get {
				EnsureDictionary ();
				IList<JsonData> values = new List<JsonData> ();

				foreach (KeyValuePair<string, JsonData> entry in
                         object_list) {
					values.Add (entry.Value);
				}

				return (ICollection)values;
			}
		}

		#endregion


		#region IJsonWrapper Properties

		bool IJsonWrapper.IsArray {
			get { return IsArray; }
		}

		bool IJsonWrapper.IsBoolean {
			get { return IsBoolean; }
		}

		bool IJsonWrapper.IsDouble {
			get { return IsDouble; }
		}

		bool IJsonWrapper.IsInt {
			get { return IsInt; }
		}

		bool IJsonWrapper.IsLong {
			get { return IsLong; }
		}

		bool IJsonWrapper.IsObject {
			get { return IsObject; }
		}

		bool IJsonWrapper.IsString {
			get { return IsString; }
		}

		#endregion


		#region IList Properties

		bool IList.IsFixedSize {
			get {
				return EnsureList ().IsFixedSize;
			}
		}

		bool IList.IsReadOnly {
			get {
				return EnsureList ().IsReadOnly;
			}
		}

		#endregion


		#region IDictionary Indexer

		object IDictionary.this [object key] {
			get {
				return EnsureDictionary () [key];
			}

			set {
				if (!(key is String))
					throw new ArgumentException (
						"The key has to be a string");

				JsonData data = ToJsonData (value);

				this [(string)key] = data;
			}
		}

		#endregion


		#region IOrderedDictionary Indexer

		object IOrderedDictionary.this [int idx] {
			get {
				EnsureDictionary ();
				return object_list [idx].Value;
			}

			set {
				EnsureDictionary ();
				JsonData data = ToJsonData (value);

				KeyValuePair<string, JsonData> old_entry = object_list [idx];

				inst_object [old_entry.Key] = data;

				KeyValuePair<string, JsonData> entry =
					new KeyValuePair<string, JsonData> (old_entry.Key, data);

				object_list [idx] = entry;
			}
		}

		#endregion


		#region IList Indexer

		object IList.this [int index] {
			get {
				return EnsureList () [index];
			}

			set {
				EnsureList ();
				JsonData data = ToJsonData (value);

				this [index] = data;
			}
		}

		#endregion


		#region Public Indexers

		public JsonData this [string prop_name] {
			get {
				EnsureDictionary ();
				return inst_object [prop_name];
			}

			set {
				EnsureDictionary ();

				KeyValuePair<string, JsonData> entry =
					new KeyValuePair<string, JsonData> (prop_name, value);

				if (inst_object.ContainsKey (prop_name)) {
					for (int i = 0; i < object_list.Count; i++) {
						if (object_list [i].Key == prop_name) {
							object_list [i] = entry;
							break;
						}
					}
				} else
					object_list.Add (entry);

				inst_object [prop_name] = value;

				json = null;
			}
		}

		public JsonData this [int index] {
			get {
				EnsureCollection ();

				if (type == JsonType.Array)
					return inst_array [index];

				return object_list [index].Value;
			}

			set {
				EnsureCollection ();

				if (type == JsonType.Array)
					inst_array [index] = value;
				else {
					KeyValuePair<string, JsonData> entry = object_list [index];
					KeyValuePair<string, JsonData> new_entry =
						new KeyValuePair<string, JsonData> (entry.Key, value);

					object_list [index] = new_entry;
					inst_object [entry.Key] = value;
				}

				json = null;
			}
		}

		#endregion


		#region Constructors

		public JsonData ()
		{
		}

		public JsonData (bool boolean)
		{
			type = JsonType.Boolean;
			inst_boolean = boolean;
		}

		public JsonData (double number)
		{
			type = JsonType.Double;
			inst_double = number;
		}

		public JsonData (int number)
		{
			type = JsonType.Int;
			inst_int = number;
		}

		public JsonData (long number)
		{
			type = JsonType.Long;
			inst_long = number;
		}

		public JsonData (object obj)
		{
			if (obj is Boolean) {
				type = JsonType.Boolean;
				inst_boolean = (bool)obj;
				return;
			}

			if (obj is Double) {
				type = JsonType.Double;
				inst_double = (double)obj;
				return;
			}

			if (obj is Int32) {
				type = JsonType.Int;
				inst_int = (int)obj;
				return;
			}

			if (obj is Int64) {
				type = JsonType.Long;
				inst_long = (long)obj;
				return;
			}

			if (obj is String) {
				type = JsonType.String;
				inst_string = (string)obj;
				return;
			}

#if UNITY3D
			if (obj is Vector2) {
				SetJsonType (JsonType.Object);
				this ["x"] = ((Vector2)obj).x;
				this ["y"] = ((Vector2)obj).y;
				return;
			}

			if (obj is Vector3) {
				SetJsonType (JsonType.Object);
				this ["x"] = ((Vector3)obj).x;
				this ["y"] = ((Vector3)obj).y;
				this ["z"] = ((Vector3)obj).z;
				return;
			}

			if (obj is Vector4) {
				SetJsonType (JsonType.Object);
				this ["x"] = ((Vector4)obj).x;
				this ["y"] = ((Vector4)obj).y;
				this ["z"] = ((Vector4)obj).z;
				this ["w"] = ((Vector4)obj).w;
				return;
			}

			if (obj is Quaternion) {
				SetJsonType (JsonType.Object);
				this ["x"] = ((Quaternion)obj).x;
				this ["y"] = ((Quaternion)obj).y;
				this ["z"] = ((Quaternion)obj).z;
				this ["w"] = ((Quaternion)obj).w;
				return;
			}

			if (obj is Matrix4x4) {
				SetJsonType (JsonType.Object);
				this ["m00"] = ((Matrix4x4)obj).m00;
				this ["m33"] = ((Matrix4x4)obj).m33;
				this ["m23"] = ((Matrix4x4)obj).m23;
				this ["m13"] = ((Matrix4x4)obj).m13;
				this ["m03"] = ((Matrix4x4)obj).m03;
				this ["m32"] = ((Matrix4x4)obj).m32;
				this ["m12"] = ((Matrix4x4)obj).m12;
				this ["m02"] = ((Matrix4x4)obj).m02;
				this ["m22"] = ((Matrix4x4)obj).m22;
				this ["m21"] = ((Matrix4x4)obj).m21;
				this ["m11"] = ((Matrix4x4)obj).m11;
				this ["m01"] = ((Matrix4x4)obj).m01;
				this ["m30"] = ((Matrix4x4)obj).m30;
				this ["m20"] = ((Matrix4x4)obj).m20;
				this ["m10"] = ((Matrix4x4)obj).m10;
				this ["m31"] = ((Matrix4x4)obj).m31;
				return;
			}
#endif

			throw new ArgumentException (
				"Unable to wrap the given object with JsonData");
		}

		public JsonData (string str)
		{
			type = JsonType.String;
			inst_string = str;
		}

		#endregion


		#region Implicit Conversions

		public static implicit operator JsonData (Boolean data)
		{
			return new JsonData (data);
		}

		public static implicit operator JsonData (Double data)
		{
			return new JsonData (data);
		}

		public static implicit operator JsonData (Int32 data)
		{
			return new JsonData (data);
		}

		public static implicit operator JsonData (Int64 data)
		{
			return new JsonData (data);
		}

		public static implicit operator JsonData (String data)
		{
			return new JsonData (data);
		}

		#endregion


		#region Explicit Conversions

		public static explicit operator Boolean (JsonData data)
		{
			if (data.type != JsonType.Boolean)
				throw new InvalidCastException (
					"Instance of JsonData doesn't hold a double");

			return data.inst_boolean;
		}

		public static explicit operator Double (JsonData data)
		{
			if (data.type != JsonType.Double)
				throw new InvalidCastException (
					"Instance of JsonData doesn't hold a double");

			return data.inst_double;
		}

		public static explicit operator Int32 (JsonData data)
		{
			if (data.type != JsonType.Int)
				throw new InvalidCastException (
					"Instance of JsonData doesn't hold an int");

			return data.inst_int;
		}

		public static explicit operator Int64 (JsonData data)
		{
			if (data.type != JsonType.Long)
				throw new InvalidCastException (
					"Instance of JsonData doesn't hold an int");

			return data.inst_long;
		}

		public static explicit operator String (JsonData data)
		{
			if (data.type != JsonType.String)
				throw new InvalidCastException (
					"Instance of JsonData doesn't hold a string");

			return data.inst_string;
		}

		#endregion


		#region ICollection Methods

		void ICollection.CopyTo (Array array, int index)
		{
			EnsureCollection ().CopyTo (array, index);
		}

		#endregion


		#region IDictionary Methods

		void IDictionary.Add (object key, object value)
		{
			JsonData data = ToJsonData (value);

			EnsureDictionary ().Add (key, data);

			KeyValuePair<string, JsonData> entry =
				new KeyValuePair<string, JsonData> ((string)key, data);
			object_list.Add (entry);

			json = null;
		}

		void IDictionary.Clear ()
		{
			EnsureDictionary ().Clear ();
			object_list.Clear ();
			json = null;
		}

		bool IDictionary.Contains (object key)
		{
			return EnsureDictionary ().Contains (key);
		}

		IDictionaryEnumerator IDictionary.GetEnumerator ()
		{
			return ((IOrderedDictionary)this).GetEnumerator ();
		}

		void IDictionary.Remove (object key)
		{
			EnsureDictionary ().Remove (key);

			for (int i = 0; i < object_list.Count; i++) {
				if (object_list [i].Key == (string)key) {
					object_list.RemoveAt (i);
					break;
				}
			}

			json = null;
		}

		#endregion


		#region IEnumerable Methods

		IEnumerator IEnumerable.GetEnumerator ()
		{
			return EnsureCollection ().GetEnumerator ();
		}

		#endregion


		#region IJsonWrapper Methods

		bool IJsonWrapper.GetBoolean ()
		{
			if (type != JsonType.Boolean)
				throw new InvalidOperationException (
					"JsonData instance doesn't hold a boolean");

			return inst_boolean;
		}

		double IJsonWrapper.GetDouble ()
		{
			if (type != JsonType.Double)
				throw new InvalidOperationException (
					"JsonData instance doesn't hold a double");

			return inst_double;
		}

		int IJsonWrapper.GetInt ()
		{
			if (type != JsonType.Int)
				throw new InvalidOperationException (
					"JsonData instance doesn't hold an int");

			return inst_int;
		}

		long IJsonWrapper.GetLong ()
		{
			if (type != JsonType.Long)
				throw new InvalidOperationException (
					"JsonData instance doesn't hold a long");

			return inst_long;
		}

		string IJsonWrapper.GetString ()
		{
			if (type != JsonType.String)
				throw new InvalidOperationException (
					"JsonData instance doesn't hold a string");

			return inst_string;
		}

		void IJsonWrapper.SetBoolean (bool val)
		{
			type = JsonType.Boolean;
			inst_boolean = val;
			json = null;
		}

		void IJsonWrapper.SetDouble (double val)
		{
			type = JsonType.Double;
			inst_double = val;
			json = null;
		}

		void IJsonWrapper.SetInt (int val)
		{
			type = JsonType.Int;
			inst_int = val;
			json = null;
		}

		void IJsonWrapper.SetLong (long val)
		{
			type = JsonType.Long;
			inst_long = val;
			json = null;
		}

		void IJsonWrapper.SetString (string val)
		{
			type = JsonType.String;
			inst_string = val;
			json = null;
		}

		string IJsonWrapper.ToJson ()
		{
			return ToJson ();
		}

		void IJsonWrapper.ToJson (JsonWriter writer)
		{
			ToJson (writer);
		}

		#endregion


		#region IList Methods

		int IList.Add (object value)
		{
			return Add (value);
		}

		void IList.Clear ()
		{
			EnsureList ().Clear ();
			json = null;
		}

		bool IList.Contains (object value)
		{
			return EnsureList ().Contains (value);
		}

		int IList.IndexOf (object value)
		{
			return EnsureList ().IndexOf (value);
		}

		void IList.Insert (int index, object value)
		{
			EnsureList ().Insert (index, value);
			json = null;
		}

		void IList.Remove (object value)
		{
			EnsureList ().Remove (value);
			json = null;
		}

		void IList.RemoveAt (int index)
		{
			EnsureList ().RemoveAt (index);
			json = null;
		}

		#endregion


		#region IOrderedDictionary Methods

		IDictionaryEnumerator IOrderedDictionary.GetEnumerator ()
		{
			EnsureDictionary ();

			return new OrderedDictionaryEnumerator (
				object_list.GetEnumerator ());
		}

		void IOrderedDictionary.Insert (int idx, object key, object value)
		{
			string property = (string)key;
			JsonData data = ToJsonData (value);

			this [property] = data;

			KeyValuePair<string, JsonData> entry =
				new KeyValuePair<string, JsonData> (property, data);

			object_list.Insert (idx, entry);
		}

		void IOrderedDictionary.RemoveAt (int idx)
		{
			EnsureDictionary ();

			inst_object.Remove (object_list [idx].Key);
			object_list.RemoveAt (idx);
		}

		#endregion


		#region Private Methods

		private ICollection EnsureCollection ()
		{
			if (type == JsonType.Array)
				return (ICollection)inst_array;

			if (type == JsonType.Object)
				return (ICollection)inst_object;

			throw new InvalidOperationException (
				"The JsonData instance has to be initialized first");
		}

		private IDictionary EnsureDictionary ()
		{
			if (type == JsonType.Object)
				return (IDictionary)inst_object;

			if (type != JsonType.None)
				throw new InvalidOperationException (
					"Instance of JsonData is not a dictionary");

			type = JsonType.Object;
			inst_object = new Dictionary<string, JsonData> ();
			object_list = new List<KeyValuePair<string, JsonData>> ();

			return (IDictionary)inst_object;
		}

		private IList EnsureList ()
		{
			if (type == JsonType.Array)
				return (IList)inst_array;

			if (type != JsonType.None)
				throw new InvalidOperationException (
					"Instance of JsonData is not a list");

			type = JsonType.Array;
			inst_array = new List<JsonData> ();

			return (IList)inst_array;
		}

		private JsonData ToJsonData (object obj)
		{
			if (obj == null)
				return null;

			if (obj is JsonData)
				return (JsonData)obj;

			return new JsonData (obj);
		}

		private static void WriteJson (IJsonWrapper obj, JsonWriter writer)
		{
			if (obj == null) {
				writer.Write (null);
				return;
			}

			if (obj.IsString) {
				writer.Write (obj.GetString ());
				return;
			}

			if (obj.IsBoolean) {
				writer.Write (obj.GetBoolean ());
				return;
			}

			if (obj.IsDouble) {
				writer.Write (obj.GetDouble ());
				return;
			}

			if (obj.IsInt) {
				writer.Write (obj.GetInt ());
				return;
			}

			if (obj.IsLong) {
				writer.Write (obj.GetLong ());
				return;
			}

			if (obj.IsArray) {
				writer.WriteArrayStart ();
				foreach (object elem in (IList) obj)
					WriteJson ((JsonData)elem, writer);
				writer.WriteArrayEnd ();

				return;
			}

			if (obj.IsObject) {
				writer.WriteObjectStart ();

				foreach (DictionaryEntry entry in ((IDictionary) obj)) {
					writer.WritePropertyName ((string)entry.Key);
					WriteJson ((JsonData)entry.Value, writer);
				}
				writer.WriteObjectEnd ();

				return;
			}
		}

		#endregion


		public int Add (object value)
		{
			JsonData data = ToJsonData (value);

			json = null;

			return EnsureList ().Add (data);
		}

		public void Clear ()
		{
			if (IsObject) {
				((IDictionary)this).Clear ();
				return;
			}

			if (IsArray) {
				((IList)this).Clear ();
				return;
			}
		}

		public bool Equals (JsonData x)
		{
			if (x == null)
				return false;

			if (x.type != this.type)
				return false;

			switch (this.type) {
			case JsonType.None:
				return true;

			case JsonType.Object:
				return this.inst_object.Equals (x.inst_object);

			case JsonType.Array:
				return this.inst_array.Equals (x.inst_array);

			case JsonType.String:
				return this.inst_string.Equals (x.inst_string);

			case JsonType.Int:
				return this.inst_int.Equals (x.inst_int);

			case JsonType.Long:
				return this.inst_long.Equals (x.inst_long);

			case JsonType.Double:
				return this.inst_double.Equals (x.inst_double);

			case JsonType.Boolean:
				return this.inst_boolean.Equals (x.inst_boolean);
			}

			return false;
		}

		public JsonType GetJsonType ()
		{
			return type;
		}

		public void SetJsonType (JsonType type)
		{
			if (this.type == type)
				return;

			switch (type) {
			case JsonType.None:
				break;

			case JsonType.Object:
				inst_object = new Dictionary<string, JsonData> ();
				object_list = new List<KeyValuePair<string, JsonData>> ();
				break;

			case JsonType.Array:
				inst_array = new List<JsonData> ();
				break;

			case JsonType.String:
				inst_string = default (String);
				break;

			case JsonType.Int:
				inst_int = default (Int32);
				break;

			case JsonType.Long:
				inst_long = default (Int64);
				break;

			case JsonType.Double:
				inst_double = default (Double);
				break;

			case JsonType.Boolean:
				inst_boolean = default (Boolean);
				break;
			}

			this.type = type;
		}

		public string ToJson ()
		{
			if (json != null)
				return json;

			StringWriter sw = new StringWriter ();
			JsonWriter writer = new JsonWriter (sw);
			writer.Validate = false;

			WriteJson (this, writer);
			json = sw.ToString ();

			return json;
		}

		public void ToJson (JsonWriter writer)
		{
			bool old_validate = writer.Validate;

			writer.Validate = false;

			WriteJson (this, writer);

			writer.Validate = old_validate;
		}

		public override string ToString ()
		{
			switch (type) {
			case JsonType.Array:
				return "JsonData array";

			case JsonType.Boolean:
				return inst_boolean.ToString ();

			case JsonType.Double:
				return inst_double.ToString ();

			case JsonType.Int:
				return inst_int.ToString ();

			case JsonType.Long:
				return inst_long.ToString ();

			case JsonType.Object:
				return "JsonData object";

			case JsonType.String:
				return inst_string;
			}

			return "Uninitialized JsonData";
		}
	}


	internal class OrderedDictionaryEnumerator : IDictionaryEnumerator
	{
		IEnumerator<KeyValuePair<string, JsonData>> list_enumerator;


		public object Current {
			get { return Entry; }
		}

		public DictionaryEntry Entry {
			get {
				KeyValuePair<string, JsonData> curr = list_enumerator.Current;
				return new DictionaryEntry (curr.Key, curr.Value);
			}
		}

		public object Key {
			get { return list_enumerator.Current.Key; }
		}

		public object Value {
			get { return list_enumerator.Current.Value; }
		}


		public OrderedDictionaryEnumerator (
			IEnumerator<KeyValuePair<string, JsonData>> enumerator)
		{
			list_enumerator = enumerator;
		}


		public bool MoveNext ()
		{
			return list_enumerator.MoveNext ();
		}

		public void Reset ()
		{
			list_enumerator.Reset ();
		}
	}
}
